1   /*
2    * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.font;
27  
28  import java.lang.ref.Reference;
29  import java.awt.FontFormatException;
30  import java.awt.geom.GeneralPath;
31  import java.awt.geom.Point2D;
32  import java.awt.geom.Rectangle2D;
33  import java.io.File;
34  import java.nio.ByteBuffer;
35  import sun.java2d.Disposer;
36  import sun.java2d.DisposerRecord;
37  
38  import java.io.IOException;
39  import java.security.AccessController;
40  import java.security.PrivilegedActionException;
41  import java.security.PrivilegedExceptionAction;
42  
43  public abstract class FileFont extends PhysicalFont {
44  
45      protected boolean useJavaRasterizer = true;
46  
47      /* I/O and file operations are always synchronized on the font
48       * object. Two threads can be accessing the font and retrieving
49       * information, and synchronized only to the extent that filesystem
50       * operations require.
51       * A limited number of files can be open at a time, to limit the
52       * absorption of file descriptors. If a file needs to be opened
53       * when there are none free, then the synchronization of all I/O
54       * ensures that any in progress operation will complete before some
55       * other thread closes the descriptor in order to allocate another one.
56       */
57      // NB consider using a RAF. FIS has finalize method so may take a
58      // little longer to be GC'd. We don't use this stream at all anyway.
59      // In fact why increase the size of a FileFont object if the stream
60      // isn't needed ..
61      //protected FileInputStream stream;
62      //protected FileChannel channel;
63      protected int fileSize;
64  
65      protected FontScaler scaler;
66  
67      /* The following variables are used, (and in the case of the arrays,
68       * only initialised) for select fonts where a native scaler may be
69       * used to get glyph images and metrics.
70       * glyphToCharMap is filled in on the fly and used to do a reverse
71       * lookup when a FileFont needs to get the charcode back from a glyph
72       * code so it can re-map via a NativeGlyphMapper to get a native glyph.
73       * This isn't a big hit in time, since a boolean test is sufficient
74       * to choose the usual default path, nor in memory for fonts which take
75       * the native path, since fonts have contiguous zero-based glyph indexes,
76       * and these obviously do all exist in the font.
77       */
78      protected boolean checkedNatives;
79      protected boolean useNatives;
80      protected NativeFont[] nativeFonts;
81      protected char[] glyphToCharMap;
82      /*
83       * @throws FontFormatException - if the font can't be opened
84       */
85      FileFont(String platname, Object nativeNames)
86          throws FontFormatException {
87  
88          super(platname, nativeNames);
89      }
90  
91      FontStrike createStrike(FontStrikeDesc desc) {
92          if (!checkedNatives) {
93             checkUseNatives();
94          }
95          return new FileFontStrike(this, desc);
96      }
97  
98      protected boolean checkUseNatives() {
99          checkedNatives = true;
100         return useNatives;
101     }
102 
103     /* This method needs to be accessible to FontManager if there is
104      * file pool management. It may be a no-op.
105      */
106     protected abstract void close();
107 
108 
109     /*
110      * This is the public interface. The subclasses need to implement
111      * this. The returned block may be longer than the requested length.
112      */
113     abstract ByteBuffer readBlock(int offset, int length);
114 
115     public boolean canDoStyle(int style) {
116         return true;
117     }
118 
119     void setFileToRemove(File file, CreatedFontTracker tracker) {
120         Disposer.addObjectRecord(this,
121                          new CreatedFontFileDisposerRecord(file, tracker));
122     }
123 
124     /* This is called when a font scaler is determined to
125      * be unusable (ie bad).
126      * We want to replace current scaler with NullFontScaler, so
127      * we never try to use same font scaler again.
128      * Scaler native resources could have already been disposed
129      * or they will be eventually by Java2D disposer.
130      * However, it should be safe to call dispose() explicitly here.
131      *
132      * For safety we also invalidate all strike's scaler context.
133      * So, in case they cache pointer to native scaler
134      * it will not ever be used.
135      *
136      * It also appears desirable to remove all the entries from the
137      * cache so no other code will pick them up. But we can't just
138      * 'delete' them as code may be using them. And simply dropping
139      * the reference to the cache will make the reference objects
140      * unreachable and so they will not get disposed.
141      * Since a strike may hold (via java arrays) native pointers to many
142      * rasterised glyphs, this would be a memory leak.
143      * The solution is :
144      * - to move all the entries to another map where they
145      *   are no longer locatable
146      * - update FontStrikeDisposer to be able to distinguish which
147      * map they are held in via a boolean flag
148      * Since this isn't expected to be anything other than an extremely
149      * rare maybe it is not worth doing this last part.
150      */
151     synchronized void deregisterFontAndClearStrikeCache() {
152         SunFontManager fm = SunFontManager.getInstance();
153         fm.deRegisterBadFont(this);
154 
155         for (Reference strikeRef : strikeCache.values()) {
156             if (strikeRef != null) {
157                 /* NB we know these are all FileFontStrike instances
158                  * because the cache is on this FileFont
159                  */
160                 FileFontStrike strike = (FileFontStrike)strikeRef.get();
161                 if (strike != null && strike.pScalerContext != 0L) {
162                     scaler.invalidateScalerContext(strike.pScalerContext);
163                 }
164             }
165         }
166         scaler.dispose();
167         scaler = FontScaler.getNullScaler();
168     }
169 
170     StrikeMetrics getFontMetrics(long pScalerContext) {
171         try {
172             return getScaler().getFontMetrics(pScalerContext);
173         } catch (FontScalerException fe) {
174             scaler = FontScaler.getNullScaler();
175             return getFontMetrics(pScalerContext);
176         }
177     }
178 
179     float getGlyphAdvance(long pScalerContext, int glyphCode) {
180         try {
181             return getScaler().getGlyphAdvance(pScalerContext, glyphCode);
182         } catch (FontScalerException fe) {
183             scaler = FontScaler.getNullScaler();
184             return getGlyphAdvance(pScalerContext, glyphCode);
185         }
186     }
187 
188     void getGlyphMetrics(long pScalerContext, int glyphCode, Point2D.Float metrics) {
189         try {
190             getScaler().getGlyphMetrics(pScalerContext, glyphCode, metrics);
191         } catch (FontScalerException fe) {
192             scaler = FontScaler.getNullScaler();
193             getGlyphMetrics(pScalerContext, glyphCode, metrics);
194         }
195     }
196 
197     long getGlyphImage(long pScalerContext, int glyphCode) {
198         try {
199             return getScaler().getGlyphImage(pScalerContext, glyphCode);
200         } catch (FontScalerException fe) {
201             scaler = FontScaler.getNullScaler();
202             return getGlyphImage(pScalerContext, glyphCode);
203         }
204     }
205 
206     Rectangle2D.Float getGlyphOutlineBounds(long pScalerContext, int glyphCode) {
207         try {
208             return getScaler().getGlyphOutlineBounds(pScalerContext, glyphCode);
209         } catch (FontScalerException fe) {
210             scaler = FontScaler.getNullScaler();
211             return getGlyphOutlineBounds(pScalerContext, glyphCode);
212         }
213     }
214 
215     GeneralPath getGlyphOutline(long pScalerContext, int glyphCode, float x, float y) {
216         try {
217             return getScaler().getGlyphOutline(pScalerContext, glyphCode, x, y);
218         } catch (FontScalerException fe) {
219             scaler = FontScaler.getNullScaler();
220             return getGlyphOutline(pScalerContext, glyphCode, x, y);
221         }
222     }
223 
224     GeneralPath getGlyphVectorOutline(long pScalerContext, int[] glyphs, int numGlyphs, float x, float y) {
225         try {
226             return getScaler().getGlyphVectorOutline(pScalerContext, glyphs, numGlyphs, x, y);
227         } catch (FontScalerException fe) {
228             scaler = FontScaler.getNullScaler();
229             return getGlyphVectorOutline(pScalerContext, glyphs, numGlyphs, x, y);
230         }
231     }
232 
233     /* T1 & TT implementation differ so this method is abstract.
234        NB: null should not be returned here! */
235     protected abstract FontScaler getScaler();
236 
237     protected long getUnitsPerEm() {
238         return getScaler().getUnitsPerEm();
239     }
240 
241     private static class CreatedFontFileDisposerRecord
242         implements DisposerRecord {
243 
244         File fontFile = null;
245         CreatedFontTracker tracker;
246 
247         private CreatedFontFileDisposerRecord(File file,
248                                               CreatedFontTracker tracker) {
249             fontFile = file;
250             this.tracker = tracker;
251         }
252 
253         public void dispose() {
254             java.security.AccessController.doPrivileged(
255                  new java.security.PrivilegedAction() {
256                       public Object run() {
257                           if (fontFile != null) {
258                               try {
259                                   if (tracker != null) {
260                                       tracker.subBytes((int)fontFile.length());
261                                   }
262                                   /* REMIND: is it possible that the file is
263                                    * still open? It will be closed when the
264                                    * font2D is disposed but could this code
265                                    * execute first? If so the file would not
266                                    * be deleted on MS-windows.
267                                    */
268                                   fontFile.delete();
269                                   /* remove from delete on exit hook list : */
270                                   // FIXME: still need to be refactored
271                                   SunFontManager.getInstance().tmpFontFiles.remove(fontFile);
272                               } catch (Exception e) {
273                               }
274                           }
275                           return null;
276                       }
277             });
278         }
279     }
280 
281     protected String getPublicFileName() {
282         SecurityManager sm = System.getSecurityManager();
283         if (sm == null) {
284             return platName;
285         }
286         boolean canReadProperty = true;
287 
288         try {
289             sm.checkPropertyAccess("java.io.tmpdir");
290         } catch (SecurityException e) {
291             canReadProperty = false;
292         }
293 
294         if (canReadProperty) {
295             return platName;
296         }
297 
298         final File f = new File(platName);
299 
300         Boolean isTmpFile = Boolean.FALSE;
301         try {
302             isTmpFile = AccessController.doPrivileged(
303                 new PrivilegedExceptionAction<Boolean>() {
304                     public Boolean run() {
305                         File tmp = new File(System.getProperty("java.io.tmpdir"));
306                         try {
307                             String tpath = tmp.getCanonicalPath();
308                             String fpath = f.getCanonicalPath();
309 
310                             return (fpath == null) || fpath.startsWith(tpath);
311                         } catch (IOException e) {
312                             return Boolean.TRUE;
313                         }
314                     }
315                 }
316             );
317         } catch (PrivilegedActionException e) {
318             // unable to verify whether value of java.io.tempdir will be
319             // exposed, so return only a name of the font file.
320             isTmpFile = Boolean.TRUE;
321         }
322 
323         return  isTmpFile ? "temp file" : platName;
324     }
325 }